1 import tkinter
2 from
tkinter import ttk
3 import subprocess
4 import json

5 from
functools import partial
6 import tkinter.messagebox
7 import socket
8 import os

9 """
10 Add close button to reports
11 pip3 download support
12 """

13 scriptdir = os.path.dirname(os.path.abspath(__file__))+
"/"
14 merrygui = None
15 fmod = {}

16
17 class
CreateToolTip(object):
18     
"""
19     create a tooltip
for a given widget
20     """

21     def __init__(self, widget, text=
'widget info'):
22         self.waittime =
500 #miliseconds
23         self.wraplength =
180 #pixels
24         self.widget = widget
25         self.text = text
26         self.widget.bind(
"<Enter>", self.enter)
27         self.widget.bind(
"<Leave>", self.leave)
28         self.widget.bind(
"<ButtonPress>", self.leave)
29         self.id = None
30         self.tw = None
31         
32     def enter(self,
event=None):
33         self.schedule()
34
35     def leave(self,
event=None):
36         self.unschedule()
37         self.hidetip()
38
39     def schedule(self):
40         self.unschedule()
41         self.id = self.widget.after(self.waittime, self.showtip)
42
43     def unschedule(self):
44         id = self.id
45         self.id = None
46         
if id:
47             self.widget.after_cancel(id)
48
49     def showtip(self,
event=None):
50         x = y =
0
51         x, y, cx, cy = self.widget.bbox(
"insert")
52         x += self.widget.winfo_rootx() +
25
53         y += self.widget.winfo_rooty() +
20
54         # creates a toplevel window
55         self.tw = tkinter.Toplevel(self.widget)
56         # Leaves only the label and removes the app window
57         self.tw.wm_overrideredirect(True)
58         self.tw.wm_geometry(
"+%d+%d" % (x, y))
59         label = tkinter.Label(self.tw, text=self.text, justify=
'left',
60                        background=
"#ffffff", relief='solid', borderwidth=1,
61                        wraplength = self.wraplength)
62         label.pack(ipadx=
1)
63
64     def hidetip(self):
65         tw = self.tw
66         self.tw= None
67         
if tw:
68             tw.destroy()
69      
70     def __str__(self):
71         
return "CreateToolTip"
72     
73 def internet(host=
"8.8.8.8", port=53, timeout=3):
74     
try:
75         socket.setdefaulttimeout(timeout)
76         socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port))
77         
return True
78     except Exception
as ex:
79         print( ex)
80         
return False
81
82 def build_package_dict(output):
83     
global fmod
84     lines = output.split(
"\n")
85     modules_outdated = lines[
2:]
86     fmod = {}
87     i =
0
88     
for item in modules_outdated:
89         f = item.split(
" ")
90         m = []
91         i +=
1
92         
for fi in f:
93             
if fi:
94                 m.append(fi)
95         
if len(m) > 0:
96             fmod[i] = m
97     
return fmod
98
99 def get_modules(host):
100     debug = False
101     
if debug:
102         output =
"""Package Version
103 ---------- ---------
104 certifi
2018.4.16
105 psutil
5.4.6
106 pycairo
1.17.0
107 PyQt5-sip
4.19.11
108 setuptools
39.2.0
109 """

110     
else:
111         res = subprocess.run([host.pip,
"list"], stdout=subprocess.PIPE)
112         output = str(res.stdout,
"latin-1")
113     data = build_package_dict(output)
114     host.modules.delete(
0, tkinter.END)
115     
for item in data:
116         host.modules.insert(tkinter.END, data[item][
0])
117     print(data)
118     host.b_update.config(state=
"disabled")
119     host.b_uninstall.config(state=
"normal")
120     
121 def get_updates(host):
122     debug = False
123     
if debug:
124         output =
"""Package Version Latest Type
125 ---------- --------- --------- -----
126 certifi
2018.4.16 2018.8.24 wheel
127 psutil
5.4.6 5.4.7 sdist
128 pycairo
1.17.0 1.17.1 sdist
129 PyQt5-sip
4.19.11 4.19.12 wheel
130 setuptools
39.2.0 40.2.0 wheel
131 """

132     
else:
133         res = subprocess.run([host.pip,
"list", "--outdated"], stdout=subprocess.PIPE)
134         output = str(res.stdout,
"latin-1")
135     data = build_package_dict(output)
136     host.modules.delete(
0, tkinter.END)
137     
if len(data) > 0:
138         
for item in data:
139             host.modules.insert(tkinter.END, data[item][
0])
140         print(data)
141         host.b_update.config(state=
"normal")
142         host.b_uninstall.config(state=
"normal")
143         tkinter.messagebox.showinfo(title=
"Result", message=f"{len(data)} updates found!")
144     
else:
145         merrygui.infolab.config(text=
"No updates found!")
146         tkinter.messagebox.showinfo(title=
"Result", message=f"No updates found!")
147     
148 def getConfig():
149     with open(
"config.json") as f:
150         
return json.load(f)
151
152 def setConfig(key:str,
value):
153     data = getConfig()
154     data[key] =
value
155     with open(
'config.json', "w") as s:
156         json.dump(data, s, indent=
4, sort_keys=True)
157
158 def dumpConfig(data):
159     with open(
'config.json', "w") as s:
160         json.dump(data, s, indent=
4, sort_keys=True)
161
162 def boolinate(
string):
163     
try:
164         truth = [
'true', '1', 'yes', 'on']
165         
if string.lower() in truth:
166             
return True
167         
else:
168             
return False
169     except:
170         
return string
171
172 def install_module(module):
173     print(
"will install "+module.get())
174     
175     
if merrygui.usermode:
176         res = subprocess.run([merrygui.pip,
"install", "--user", module.get()], stdout=subprocess.PIPE)
177     
else:
178         res = subprocess.run([merrygui.pip,
"install", module.get()], stdout=subprocess.PIPE)
179     output = str(res.stdout,
"latin-1")
180     #tkinter.messagebox.showinfo(title=
"Result", message=output)
181     r = tkinter.Tk()
182     lb = tkinter.Label(r, text=output, justify=
"left")
183     lb.grid()
184     r.title(
"Result")
185     r.mainloop()
186
187 def search_module(module):
188     print(
"will search "+module.get())
189     
190     res = subprocess.run([merrygui.pip,
"search", module.get()], stdout=subprocess.PIPE)
191     output = str(res.stdout,
"latin-1")
192     #tkinter.messagebox.showinfo(title=
"Result", message=output)
193     r = tkinter.Tk()
194     lb = tkinter.Label(r, text=output, justify=
"left")
195     lb.grid()
196     r.title(
"Result")
197     r.mainloop()
198     
199         
200 def install():
201     w = tkinter.Tk()
202     en = tkinter.Entry(w)
203     run_inst =
partial(install_module, en)
204     run_srch =
partial(search_module, en)
205     b = tkinter.Button(w, text=
"Install", command=run_inst, cursor="hand1")
206     sr = tkinter.Button(w, text=
"Search", command=run_srch, cursor="hand1")
207     en.grid(columnspan=
2)
208     b.grid(row=
1)
209     sr.grid(row=
1, column=1)
210     w.title(
"Installer")
211     w.mainloop()
212     
213 def uninstall():
214     mod = merrygui.modules.curselection()[
0]
215     mod +=
1
216     
if tkinter.messagebox.askokcancel(title=f"Uninstall {fmod[mod][0]}", message=f"{fmod[mod][0]} {fmod[mod][1]} will be COMPLETELY uninstalled."):
217         res = subprocess.run([merrygui.pip,
"uninstall", "-y", fmod[mod][0]], stdout=subprocess.PIPE)
218         output = str(res.stdout,
"latin-1")
219         merrygui.modules.delete(mod-
1)
220         r = tkinter.Tk()
221         lb = tkinter.Label(r, text=output)
222         lb.grid()
223         r.title(
"Result")
224         r.mainloop()
225         
226 def update():
227     mod = merrygui.modules.curselection()[
0]
228     mod +=
1
229     print(fmod[mod][
0])
230     
if tkinter.messagebox.askokcancel(title=f"Update {fmod[mod][0]}", message=f"{fmod[mod][0]} will be updated from {fmod[mod][1]} to {fmod[mod][2]}"):
231         
if merrygui.usermode:
232             res = subprocess.run([merrygui.pip,
"install", "--upgrade", fmod[mod][0]], stdout=subprocess.PIPE)
233         
else:
234             res = subprocess.run([merrygui.pip,
"install", "--upgrade", "--user", fmod[mod][0]], stdout=subprocess.PIPE)
235         output = str(res.stdout,
"latin-1")
236         merrygui.modules.delete(mod-
1)
237         r = tkinter.Tk()
238         lb = tkinter.Label(r, text=output, justify=
"left")
239         lb.grid()
240         r.title(
"Result")
241         r.mainloop
242         
243 def onselect(evt):
244     w = evt.widget
245     
try:
246         index =
int(w.curselection()[0])
247     except IndexError:
248         
return
249     index +=
1
250     #
value = w.get(index)
251     
try:
252         merrygui.infolab.config(text=fmod[index][
0]+" - Current Version: "+fmod[index][1]+" - PIP Version: "+fmod[index][2])
253     except:
254         merrygui.infolab.config(text=fmod[index][
0]+" "+fmod[index][1])
255
256 def reconnect():
257     
if internet():
258         merrygui.b_updatecheck.config(state=
"normal")
259         merrygui.b_install.config(state=
"normal")
260         merrygui.b_rec.destroy()
261         merrygui.online = True
262         merrygui.infolab.config(text=
"Reconnected to network!")
263     
else:
264         tkinter.messagebox.showerror(title=
"No network connection", message="No internet connection was found.\nMerry will run in offline mode. (No update checking.)")
265
266 def pipcheck():
267     res = subprocess.run([merrygui.pip,
"check"], stdout=subprocess.PIPE)
268     output = str(res.stdout,
"latin-1")
269     r = tkinter.Tk()
270     lb = tkinter.Label(r, text=output)
271     lb.grid()
272     r.title(
"Result")
273     r.mainloop()
274     
275 def pipshow():
276     
try:
277         mod = merrygui.modules.curselection()[
0]
278     except IndexError:
279         tkinter.messagebox.showerror(title=
"Error", message="No package selected.")
280         
return
281     mod +=
1
282     res = subprocess.run([merrygui.pip,
"show", fmod[mod][0]], stdout=subprocess.PIPE)
283     output = str(res.stdout,
"latin-1")
284     r = tkinter.Tk()
285     lb = tkinter.Label(r, text=output, justify=
"left")
286     lb.grid()
287     r.title(
"Result")
288     r.mainloop()
289         
290 def about():
291     tkinter.messagebox.showinfo(title=
"About Merry", message="Merry is a pip GUI interface written by Kaiser.\nSource is available at https://github.com/Kaiz0r/Merry")
292     

293 class
pipGuiMan:
294     def __init__(self):
295         self.online = internet()
296         self.config = getConfig()
297         self.pip = self.config[
'pip_command']
298         self.update_check_on_start = boolinate(self.config[
'auto_update_check'])
299         self.usermode = boolinate(self.config[
'add_user_flag'])
300         self.mainwin = tkinter.Tk()
301         self.modules = tkinter.Listbox(self.mainwin, height=
15)
302         
303         self.modules.grid(rowspan=
6, columnspan=4)
304
305         self.modules.bind(
'<<ListboxSelect>>', onselect)
306         ub =
partial(get_updates, self)
307         ubi =
partial(get_modules, self)
308         self.infolab = tkinter.Label(self.mainwin, text=
"Selected info will appear here.")
309         self.infolab.grid(row=
6, columnspan=6)
310         self.chicon = tkinter.PhotoImage(file=os.path.
join(scriptdir,'py.png'))
311         self.listicon = tkinter.PhotoImage(file=os.path.
join(scriptdir,'list.png'))
312         self.dlicon = tkinter.PhotoImage(file=os.path.
join(scriptdir,'dl.png'))
313         self.unicon = tkinter.PhotoImage(file=os.path.
join(scriptdir,'uni.png'))
314         self.upicon = tkinter.PhotoImage(file=os.path.
join(scriptdir,'upg.png'))
315         self.b_updatecheck = tkinter.Button(self.mainwin, image=self.chicon, compound=
"left", text="Check for updates", command=ub, cursor="hand1", width=150, anchor="w")
316         self.b_listall = tkinter.Button(self.mainwin, text=
"Show list", image=self.listicon, compound="left", command=ubi, cursor="hand1", width=150, anchor="w")
317         self.b_install = tkinter.Button(self.mainwin, image=self.dlicon, compound=
"left", text="Install...", command=install, cursor="hand1", width=150, anchor="w")
318         self.b_uninstall = tkinter.Button(self.mainwin, image=self.unicon, compound=
"left", text="Uninstall", command=uninstall, state="disabled", cursor="hand1", width=150, anchor="w")
319         self.b_update = tkinter.Button(self.mainwin, image=self.upicon, compound=
"left", text="Update", command=update, state="disabled", cursor="hand1", width=150, anchor="w")
320         
321         self.b_updatecheck.grid(column=
4, row=0)
322         CreateToolTip(self.b_updatecheck,
"Gets outdated modules list.\nNOTE: Will take a few moments.")
323         self.b_listall.grid(column=
4, row=1)
324         CreateToolTip(self.b_listall,
"Gets installed modules list.\nNOTE: Will take a few moments.")
325         self.b_install.grid(column=
4, row=2)
326         CreateToolTip(self.b_install,
"Opens the Installer window. Enter a module name to download and install using pip.")
327         self.b_uninstall.grid(column=
4, row=3)
328         CreateToolTip(self.b_uninstall,
"Completely uninstalls the module selected in the list.")
329         self.b_update.grid(column=
4, row=4)
330         CreateToolTip(self.b_update,
"Updates the selected module in the list.")
331         self.mainwin.title(
"Merry")
332         imgicon = tkinter.PhotoImage(file=os.path.
join(scriptdir,'icon.png'))
333         self.mainwin.tk.call(
'wm', 'iconphoto', self.mainwin, imgicon)
334         self.menu = tkinter.Menu(self.mainwin)
335         self.mainwin.config(menu=self.menu)
336
337         self.filemenu = tkinter.Menu(self.mainwin)
338         self.menu.add_cascade(label=
"Merry", menu=self.filemenu)
339         self.filemenu.add_command(label=
"About", command=about)
340         self.filemenu.add_command(label=
"Check Libraries integrity", command=pipcheck)
341         self.filemenu.add_command(label=
"Show info on selected package", command=pipshow)
342         self.filemenu.add_separator()
343         self.filemenu.add_command(label=
"Exit", command=self.mainwin.destroy)
344         
345         
if not self.online:
346             self.b_updatecheck.config(state=
"disabled")
347             self.b_install.config(state=
"disabled")
348             self.bicon = tkinter.PhotoImage(file=os.path.
join(scriptdir,'reset.png'))
349             self.b_rec = tkinter.Button(self.mainwin, image=self.bicon, command=reconnect)
350             self.b_rec.grid(row=
0, column=5)
351             CreateToolTip(self.b_rec,
"Reconnect to network.")
352             self.infolab.config(text=
"No internet connection was found.\nMerry will run in offline mode. (No update checking.)")
353             
354         
if self.update_check_on_start and self.online:
355             get_updates(self)
356             
357 merrygui = pipGuiMan()
358 merrygui.mainwin.mainloop()
359 print(merrygui.pip)


Gõ tìm kiếm nhanh...